package com.nutiteq.directions;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.midlet.MIDletStateChangeException;

import com.nutiteq.BasicMapComponent;
import com.nutiteq.cache.CachingChain;
import com.nutiteq.cache.MemoryCache;
import com.nutiteq.cache.RmsCache;
import com.nutiteq.components.Line;
import com.nutiteq.components.LineStyle;
import com.nutiteq.components.OnMapElement;
import com.nutiteq.components.Place;
import com.nutiteq.components.PlaceIcon;
import com.nutiteq.components.Placemark;
import com.nutiteq.components.Route;
import com.nutiteq.components.RouteInstruction;
import com.nutiteq.components.WgsPoint;
import com.nutiteq.controls.ControlKeys;
import com.nutiteq.controls.OnScreenZoomControls;
import com.nutiteq.listeners.MapListener;
import com.nutiteq.listeners.OnMapElementListener;
import com.nutiteq.maps.CloudMade;
import com.nutiteq.maps.NutiteqStreamedMap;
import com.nutiteq.maps.OpenStreetMap;
import com.nutiteq.services.DirectionsService;
import com.nutiteq.services.DirectionsWaiter;
import com.nutiteq.ui.DefaultCursor;
import com.nutiteq.ui.StringCopyright;
import com.nutiteq.ui.ThreadDrivenPanning;
import com.nutiteq.utils.DummyStarter;
import com.nutiteq.utils.InitializedCanvas;
import com.nutiteq.utils.LogForm;
import com.nutiteq.utils.Utils;

public class DirectionsCanvas extends Canvas implements MapListener, CommandListener,
    OnMapElementListener, InitializedCanvas, DirectionsWaiter {
  public static DirectionsCanvas instance;

  private final BasicMapComponent mapComponent;

  private final Command exit = new Command("Exit", Command.EXIT, 0);
  private final Command directionsOptions = new Command("Directions", Command.ITEM, 0);
  private final Command clearError = new Command("Clear error", Command.ITEM, 0);
  private final Command lineProperties = new Command("Line properties", Command.ITEM, 0);
  private final Command cleanMap = new Command("Clean map", Command.ITEM, 0);

  private final Image[] markers;

  private static final int MARKER_WIDTH = 10;
  private static final int MARKER_HEIGHT = 17;

  private static final int MARKER_START = 0;
  private static final int MARKER_END = 1;

  private Place start;
  private WgsPoint startCoordinates;
  private Place end;
  private WgsPoint endCoordinates;

  private Line directionsLine;

  private String[] errorMessage;
  private final Font defaultFont = Font.getDefaultFont();

  private Image splash;

  private final Timer timer;
  private Thread starter;

  private int lineWidth = 4;
  private int lineColor = 0xFF0000FF;

  private boolean initialized;

  private WgsPoint[] points;

  private Place[] instructionPlaces;

  private final Placemark yellow;

  public DirectionsCanvas() {
    instance = this;

    final Image tmp = Utils.createImage("/markers.png");
    markers = new Image[2];
    for (int i = 0; i < markers.length; i++) {
      markers[i] = Image.createImage(tmp, i * MARKER_WIDTH, 0, MARKER_WIDTH, MARKER_HEIGHT,
          Sprite.TRANS_NONE);
    }

    yellow = new PlaceIcon(Image.createImage(tmp, 20, 0, 10, 17, 0), 5, 17);

    try {
      splash = Image.createImage("/splash.png");
    } catch (final IOException e) {
      e.printStackTrace();
    }

    timer = new Timer();
    timer.schedule(new TimerTask() {
      public void run() {
        starter = new Thread(new DummyStarter(Directions.instance, instance));
        starter.start();
      }
    }, 3000);

    mapComponent = new BasicMapComponent("abcdtrial", Directions.instance, getWidth(), getHeight(),
        new WgsPoint(24.764580, 59.437420), 5);
  }

  public void initialize() {
//    final CloudMade map = new CloudMade("B37E70903EB540C1A07B3437AFA5DF2E", "testUser", 64, 1);
//    map.setCopyright(new StringCopyright("© 2009 CloudMade", StringCopyright.COPYRIGHT_FONT_SMALL));
//    mapComponent.setMap(map);
    mapComponent.setMap(NutiteqStreamedMap.OPENSTREETMAP);
    mapComponent.setPanningStrategy(new ThreadDrivenPanning());

    final MemoryCache memoryCache = new MemoryCache(50 * 1024);
    final RmsCache rmsCache = new RmsCache("ML_NETWORK_CACHE", 64 * 1024, 5);
    mapComponent.setNetworkCache(new CachingChain(new com.nutiteq.cache.Cache[] { memoryCache,
        rmsCache }));

    mapComponent.setMapListener(this);
    mapComponent.setOnMapElementListener(this);

    if (hasPointerEvents()) {
      mapComponent.setOnScreenZoomControls(new OnScreenZoomControls(Utils
          .createImage(OnScreenZoomControls.DEFAULT_ZOOM_IMAGE)));
    } else {
      mapComponent.setCursor(new DefaultCursor(0xFFFF0000));
    }

    mapComponent.defineControlKey(ControlKeys.MOVE_UP_KEY, -1);
    mapComponent.defineControlKey(ControlKeys.MOVE_DOWN_KEY, -2);
    mapComponent.defineControlKey(ControlKeys.MOVE_LEFT_KEY, -3);
    mapComponent.defineControlKey(ControlKeys.MOVE_RIGHT_KEY, -4);
    mapComponent.defineControlKey(ControlKeys.SELECT_KEY, KEY_NUM5);
    //MPowerPlayer
    mapComponent.defineControlKey(ControlKeys.MOVE_UP_KEY, 38);
    mapComponent.defineControlKey(ControlKeys.MOVE_DOWN_KEY, 40);
    mapComponent.defineControlKey(ControlKeys.MOVE_LEFT_KEY, 37);
    mapComponent.defineControlKey(ControlKeys.MOVE_RIGHT_KEY, 39);
    mapComponent.defineControlKey(ControlKeys.SELECT_KEY, 10);
    //for motorola
    mapComponent.defineControlKey(ControlKeys.MOVE_UP_KEY, KEY_NUM2);
    mapComponent.defineControlKey(ControlKeys.MOVE_DOWN_KEY, KEY_NUM8);
    mapComponent.defineControlKey(ControlKeys.MOVE_LEFT_KEY, KEY_NUM4);
    mapComponent.defineControlKey(ControlKeys.MOVE_RIGHT_KEY, KEY_NUM6);
    mapComponent.defineControlKey(ControlKeys.SELECT_KEY, KEY_NUM5);

    mapComponent.startMapping();

    //mapComponent.addKmlService(new KmlUrlReader("http://lbs.nutiteq.ee/mpark_tln_trt.kml", false));

    addCommand(exit);
    addCommand(LogForm.SHOW_LOG);
    addCommand(lineProperties);
    setCommandListener(this);
    initialized = true;
    repaint();
  }

  protected void paint(final Graphics g) {
    if (!initialized) {
      //paint splash
      g.setColor(0xFFFFFFFF);
      g.setClip(0, 0, getWidth(), getHeight());
      g.fillRect(0, 0, getWidth(), getHeight());
      g.drawImage(splash, (getWidth() - splash.getWidth()) / 2,
          (getHeight() - splash.getHeight()) / 2, Graphics.TOP | Graphics.LEFT);
      return;
    }

    g.setClip(0, 0, getWidth(), getHeight());
    if (errorMessage == null) {
      mapComponent.paint(g);
    } else {
      g.setColor(0xFFFFFFFF);
      g.fillRect(0, 0, getWidth(), getHeight());
      g.setColor(0xFFFF0000);
      g.setFont(defaultFont);
      int paintY = 20;
      for (int i = 0; i < errorMessage.length; i++) {
        final int width = defaultFont.stringWidth(errorMessage[i]);
        final int paintX = (getWidth() - width) / 2;
        g.drawString(errorMessage[i], paintX, paintY, Graphics.TOP | Graphics.LEFT);
        paintY += defaultFont.getHeight();
      }
    }
  }

  public void mapClicked(final WgsPoint p) {
    Place added;
    Place removed = null;
    if (start == null) {
      added = createPlace(MARKER_START, p);
      startCoordinates = p;
      start = added;
    } else {
      added = createPlace(MARKER_END, p);
      removed = end;
      end = added;
      endCoordinates = p;
      addCommand(directionsOptions);
    }
    mapComponent.addPlace(added);
    if (removed != null) {
      mapComponent.removePlace(removed);
    }
  }

  private Place createPlace(final int markerType, final WgsPoint p) {
    return new Place(markerType, markerType == MARKER_START ? "Start" : "End", new PlaceIcon(
        markers[markerType], MARKER_WIDTH / 2, MARKER_HEIGHT), p);
  }

  public void mapMoved() {
    // IGNORE
  }

  public void needRepaint(final boolean mapIsComplete) {
    repaint();
  }

  public void commandAction(final Command cmd, final Displayable d) {
    if (cmd == exit) {
      mapComponent.stopMapping();
      try {
        Directions.instance.destroyApp(true);
      } catch (final MIDletStateChangeException ignore) {
      }
    } else if (cmd == directionsOptions) {
      if (directionsLine != null) {
        mapComponent.removeLine(directionsLine);
      }
      show(new RoutingServiceList(this, startCoordinates, endCoordinates));
    } else if (cmd == clearError) {
      errorMessage = null;
      removeCommand(clearError);
      repaint();
    } else if (cmd == LogForm.SHOW_LOG) {
      Display.getDisplay(Directions.instance).setCurrent(
          new LogForm(mapComponent, Directions.instance, this));
    } else if (cmd == lineProperties) {
      Display.getDisplay(Directions.instance).setCurrent(
          new LinePropertiesForm(this, lineWidth, lineColor));
    } else if (cmd == cleanMap) {
      cleanMap();
    }
  }

  public void show(final Displayable next) {
    Display.getDisplay(Directions.instance).setCurrent(next);
  }

  protected void keyPressed(final int keyCode) {
    if (!initialized) {
      return;
    }
    mapComponent.keyPressed(keyCode);
  }

  protected void keyReleased(final int keyCode) {
    if (!initialized) {
      return;
    }
    mapComponent.keyReleased(keyCode);
  }

  protected void keyRepeated(final int keyCode) {
    if (!initialized) {
      return;
    }
    mapComponent.keyRepeated(keyCode);
  }

  protected void pointerDragged(final int x, final int y) {
    if (!initialized) {
      return;
    }
    mapComponent.pointerDragged(x, y);
  }

  protected void pointerPressed(final int x, final int y) {
    if (!initialized) {
      return;
    }
    mapComponent.pointerPressed(x, y);
  }

  protected void pointerReleased(final int x, final int y) {
    if (!initialized) {
      return;
    }
    mapComponent.pointerReleased(x, y);
  }

  public void elementClicked(final OnMapElement p) {
    if (p != start && p != end) {
      return;
    }

    cleanMap();
  }

  private void cleanMap() {
    mapComponent.removePlace(start);
    start = null;
    mapComponent.removePlace(end);
    end = null;
    removeCommand(directionsOptions);
    mapComponent.removeLine(directionsLine);
    mapComponent.removePlaces(instructionPlaces);
    directionsLine = null;
    removeCommand(cleanMap);
  }

  public void elementEntered(final OnMapElement p) {
    // ignore
  }

  public void elementLeft(final OnMapElement p) {
    // ignore
  }

  public void setLineStyle(final int width, final int color) {
    lineWidth = width;
    lineColor = color;
    if (directionsLine != null) {
      mapComponent.removeLine(directionsLine);
      directionsLine = new Line(points, new LineStyle(lineColor, lineWidth));
      mapComponent.addLine(directionsLine);
    }
  }

  public void placeHidden(final Place p) {
    // ignore
  }

  public void placeVisible(final Place p) {
    // ignore
  }

  public void networkError() {
    showError("Network error!");
  }

  public void routeFound(final Route route) {
    directionsLine = route.getRouteLine();
    points = directionsLine.getPoints();
    mapComponent.addLine(directionsLine);
    removeCommand(directionsOptions);
    final RouteInstruction[] instructions = route.getInstructions();
    instructionPlaces = new Place[instructions.length];
    for (int i = 0; i < instructions.length; i++) {
      instructionPlaces[i] = new Place(i, instructions[i].getInstruction(), yellow, instructions[i]
          .getPoint());
    }
    mapComponent.addPlaces(instructionPlaces);
    if (instructionPlaces.length > 0) {
      //TODO jaanus : check this
      mapComponent.removePlace(start);
    }
    addCommand(cleanMap);
    show(this);
  }

  public void routingErrors(final int errorCodes) {
    final StringBuffer errors = new StringBuffer("Errors: ");
    if ((errorCodes & DirectionsService.ERROR_DESTINATION_ADDRESS_NOT_FOUND) != 0) {
      errors.append("destination not found,");
    }
    if ((errorCodes & DirectionsService.ERROR_FROM_ADDRESS_NOT_FOUND) != 0) {
      errors.append("from not found,");
    }
    if ((errorCodes & DirectionsService.ERROR_FROM_AND_DESTINATION_ADDRESS_SAME) != 0) {
      errors.append("from and destination same,");
    }
    if ((errorCodes & DirectionsService.ERROR_ROUTE_NOT_FOUND) != 0) {
      errors.append("route not found,");
    }
    showError(errors.toString());
  }

  public void routingParsingError(final String message) {
    showError(message);
  }

  public void setDirectionsService(final DirectionsService directions) {
    show(new WaitForm());
    directions.execute();
  }

  private void showError(final String string) {
    errorMessage = Utils.wrapText(string, defaultFont, getWidth() - 20);
    show(this);
    addCommand(clearError);
  }
}
